package types

import (
	"encoding/binary"
	"fmt"
	"math"
	"strconv"
)

const (
	FieldTypeString  = "string"
	FieldTypeInt32   = "int32"
	FieldTypeInt64   = "int64"
	FieldTypeFloat32 = "float32"
	FieldTypeFloat64 = "float64"
)

type FieldDesc struct {
	Name string `json:"name"`
	Type string `json:"type"`
}

type Field interface {
	Ref() interface{}
	Bytes() []byte
	AsString() string
}

type StringField struct {
	value string
}

func (f *StringField) AsString() string {
	return f.value
}

func (f *StringField) Ref() interface{} {
	return &f.value
}

func (f *StringField) Bytes() []byte {
	length := len(f.value)
	bytes := make([]byte, 4, 4+length)
	binary.LittleEndian.PutUint32(bytes, uint32(length))
	bytes = append(bytes, []byte(f.value)...)
	return bytes
}

type Int32Field struct {
	value int32
}

func (f *Int32Field) AsString() string {
	return strconv.FormatInt(int64(f.value), 10)
}

func (f *Int32Field) Ref() interface{} {
	return &f.value
}

func (f *Int32Field) Bytes() []byte {
	bytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(bytes, uint32(f.value))
	return bytes
}

type Int64Field struct {
	value int64
}

func (f *Int64Field) AsString() string {
	return strconv.FormatInt(f.value, 10)
}

func (f *Int64Field) Ref() interface{} {
	return &f.value
}

func (f *Int64Field) Bytes() []byte {
	bytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(bytes, uint64(f.value))
	return bytes
}

type Float32Field struct {
	value float32
}

func (f *Float32Field) AsString() string {
	return fmt.Sprintf("%f", f.value)
}

func (f *Float32Field) Ref() interface{} {
	return &f.value
}

func (f *Float32Field) Bytes() []byte {
	bytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(bytes, math.Float32bits(f.value))
	return bytes
}

type Float64Field struct {
	value float64
}

func (f *Float64Field) AsString() string {
	return fmt.Sprintf("%f", f.value)
}

func (f *Float64Field) Ref() interface{} {
	return &f.value
}

func (f *Float64Field) Bytes() []byte {
	bytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(bytes, math.Float64bits(f.value))
	return bytes
}

func CreateField(fieldType string) (Field, error) {
	switch fieldType {
	case FieldTypeString:
		return &StringField{}, nil
	case FieldTypeInt32:
		return &Int32Field{}, nil
	case FieldTypeInt64:
		return &Int64Field{}, nil
	case FieldTypeFloat32:
		return &Float32Field{}, nil
	case FieldTypeFloat64:
		return &Float64Field{}, nil
	default:
		return nil, fmt.Errorf("unsupported field type: %s", fieldType)
	}
}

func NewFields(fieldDescList []FieldDesc) ([]Field, error) {
	fields := make([]Field, 0, len(fieldDescList))
	for _, fieldDesc := range fieldDescList {
		field, err := CreateField(fieldDesc.Type)
		if err != nil {
			return nil, err
		}
		fields = append(fields, field)
	}
	return fields, nil
}
